#!/bin/sh

export EXTENSIONS_REPOSITORY_URL="${EXTENSIONS_REPOSITORY_URL:-https://extensions.stackgres.io/postgres/repository}"
EXTENSIONS_ARCH="$(uname -m)"
EXTENSIONS_CACHE_PRELOADED_EXTENSIONS="$(
  {
    echo "$EXTENSIONS_ARCH/linux/timescaledb-1\.7\.4-pg12"
    echo "$EXTENSIONS_ARCH/linux/intagg-.*-pg12"
    echo "$EXTENSIONS_ARCH/linux/pageinspect-.*-pg12"
    echo "$EXTENSIONS_ARCH/linux/pg_repack-.*-pg(12|13)"
    echo "$EXTENSIONS_ARCH/linux/postgis-.*-pg(12|13)"
    echo "$EXTENSIONS_ARCH/linux/postgis_raster-.*-pg12(12|13)"
  } | jq -R . | jq -s -c .
  )"
EXTENSIONS_METADATA_VERSION=v2
EXTENSIONS_DEFAULT_FLAVOR=pg
EXTENSIONS_DEFAULT_BUILD_ARCH=x86_64
EXTENSIONS_DEFAULT_BUILD_OS=linux
EXTENSIONS_INDEX_CACHED="${EXTENSIONS_INDEX_CACHED:-false}"
export EXTENSIONS_INDEX_CACHED

get_extensions_metadata_url() {
  local NAME="$1"
  printf '%s' "$(printf '%s' "${EXTENSIONS_REPOSITORY_URL%%\?*}" | sed 's#/\+$##')/$EXTENSIONS_METADATA_VERSION/$NAME"
}

get_extensions_index_hash() {
  cache_extensions_index
  md5sum "$TARGET_PATH/extensions-index.json" | cut -d ' ' -f 1
}

cache_extensions_index() {
  if [ "$EXTENSIONS_INDEX_CACHED" != true ] || [ ! -f "$TARGET_PATH/extensions-index.json" ]
  then
    curl -s -f "$(get_extensions_metadata_url index.json)" \
      > "$TARGET_PATH/extensions-index.json"
    if [ ! -f "$TARGET_PATH/extensions-unwrapped-hashes.json" ]
    then
      curl -s -f "$(get_extensions_metadata_url hashes.json)" \
        > "$TARGET_PATH/extensions-hashes.json"
      jq '
        .publishers = (.publishers | map(
          {
            key: .id,
            value: .
          }
          ) | from_entries)
        | .extensions = (.extensions | map(
          {
            key: .name,
            value: .
          }
          ) | from_entries)
        ' "$TARGET_PATH/extensions-hashes.json" > "$TARGET_PATH/extensions-unwrapped-hashes.json"
    fi
    EXTENSIONS_INDEX_CACHED=true
  fi
}

get_latest_version_of_extension() {
  get_versions_of_extension "$1" "$2" "$3" | head -n 1
}

get_oldest_version_of_extension() {
  get_versions_of_extension "$1" "$2" "$3" | tail -n 1
}

get_versions_of_extension() {
  local EXTENSION_NAME="$1"
  local POSTGRES_VERSION="$2"
  local FLAVOR="${3:-pg}"
  local VERSIONS

  cache_extensions_index

  PATRONI_IMAGE="$(get_component_images "$STACKGRES_VERSION")"
  PATRONI_IMAGE="$(printf '%s' "$PATRONI_IMAGE" | grep "/patroni:.*-$FLAVOR$POSTGRES_VERSION")"
  PATRONI_IMAGE="$(printf '%s' "$PATRONI_IMAGE" | sort | tail -n 1)"
  PATRONI_IMAGE_TAG="${PATRONI_IMAGE##*:}"
  PATRONI_IMAGE_POSTGRES_VERSION_WITH_BUILD_VERSION="${PATRONI_IMAGE##*-$FLAVOR}"
  PATRONI_IMAGE_POSTGRES_VERSION="${PATRONI_IMAGE_POSTGRES_VERSION_WITH_BUILD_VERSION%%-build-*}"
  PATRONI_IMAGE_POSTGRES_MAJOR_VERSION="${PATRONI_IMAGE_POSTGRES_VERSION%%.*}"
  PATRONI_IMAGE_BUILD_VERSION="${PATRONI_IMAGE_POSTGRES_VERSION_WITH_BUILD_VERSION##*-build-}"
  PATRONI_IMAGE_BUILD_MAJOR_VERSION="${PATRONI_IMAGE_BUILD_VERSION%%.*}"
  EXTENSIONS_ARCH="$(uname -m)"
  VERSIONS="$(jq -r "$(cat << EOF
.extensions[] | select(.name == "$EXTENSION_NAME").versions
  | sort_by(.version|split(".")) | reverse[]
  | select((.availableFor
    | map(select(
      (.postgresVersion == "$PATRONI_IMAGE_POSTGRES_VERSION"
        or .postgresVersion == "$PATRONI_IMAGE_POSTGRES_MAJOR_VERSION")
      and (if .build == null then true
        else .build | split(".")[0] == "$PATRONI_IMAGE_BUILD_MAJOR_VERSION" end)
      and ((.arch == null and "$EXTENSIONS_ARCH" == "x86_64") or (.arch == "$EXTENSIONS_ARCH"))))
      | length) > 0).version
EOF
      )" "$TARGET_PATH/extensions-index.json")"
  if [ -z "$VERSIONS" ]
  then
    echo "Can not find any compatible version of extension $EXTENSION_NAME for postgres $PATRONI_IMAGE_POSTGRES_VERSION build $PATRONI_IMAGE_BUILD_MAJOR_VERSION" >&2
    return 1
  fi
  echo "$VERSIONS"
}

get_preloaded_extensions() {
  if [ "$EXTENSIONS_CACHE_ENABLED" != true ] \
    || ! is_image_repository_url "$EXTENSIONS_REPOSITORY_URL"
  then
    return
  fi

  cache_extensions_index

  if ! jq -r '
    .extensions[] | . as $extension
    | .versions[] | . as $version
    | .availableFor = (.availableFor
      | sort_by(if .build == null then 0 else (.build | split(".")
        | (.[0] | tonumber | . * 10000) + (.[1] | split("-")[0] | tonumber)) end)
      | reduce .[] as $availableForEntry ({};
        . as $result | ($availableForEntry.postgresVersion | if . != null then . else "any" end) as $key
        | $availableForEntry | $result | .[$key] = $availableForEntry) | to_entries | map(.value))
    | .availableFor[] | . as $availableFor
    | select('"$EXTENSIONS_CACHE_PRELOADED_EXTENSIONS"' | any(. as $test
      | ($availableFor.arch | if . != null then . else "'"$EXTENSIONS_DEFAULT_BUILD_ARCH"'" end)
        + "/" + ($availableFor.os | if . != null then . else "'"$EXTENSIONS_DEFAULT_BUILD_OS"'" end)
        + "/" + $extension.name + "-" + $version.version + "-"
        + ($availableFor.flavor | if . != null then . else "'"$EXTENSIONS_DEFAULT_FLAVOR"'" end)
        + $availableFor.postgresVersion
        + ($availableFor.build | if . != null then "-build-" + . else "" end)
      | test($test; "")))
    | ($extension.repository | if . != null then . else '"$(
      printf '%s' "${EXTENSIONS_REPOSITORY_URL%%\?*}" | jq -sR .)"' end) + " "
      + $extension.publisher + " "
      + $extension.name + " "
      + $version.version + " "
      + $availableFor.postgresVersion
      + " " + ($availableFor.flavor | if . != null then . else "'"$EXTENSIONS_DEFAULT_FLAVOR"'" end)
      + " " + ($availableFor.arch | if . != null then . else "'"$EXTENSIONS_DEFAULT_BUILD_ARCH"'" end)
      + " " + ($availableFor.os | if . != null then . else "'"$EXTENSIONS_DEFAULT_BUILD_OS"'" end)
      + " " + ($availableFor.build | if . != null then . else "" end)' \
    "$TARGET_PATH/extensions-index.json"
  then
    echo "Error while applying filter $EXTENSIONS_CACHE_PRELOADED_EXTENSIONS to preload extensions" >&2
    return 1
  fi
}

get_extension_image() {
  [ -n "$1" ] && [ -n "$2" ] && [ -n "$3" ] && [ -n "$4" ] \
    && [ -n "$5" ] && [ -n "$6" ] && [ -n "$7" ] && [ -n "$8" ]
  local EXTENSIONS_REPOSITORY_URL="$1"
  local PUBLISHER="$2"
  local NAME="$3"
  local VERSION="$4"
  local POSTGRES_VERSION="$5"
  local FLAVOR="$6"
  local BUILD_ARCH="$7"
  local BUILD_OS="$8"
  local BUILD="$9"
  local IMAGE_TEMPLATE COMPONENT COMPONENT_VERSION HASH IMAGE_NAME CONTAINER STATEFULSET
  IMAGE_TEMPLATE="$(get_image_template_from_url "$EXTENSIONS_REPOSITORY_URL")"
  COMPONENT="$(jq -r ".extensions[\"$NAME\"]
    | .component" "$TARGET_PATH/extensions-unwrapped-hashes.json")"
  # shellcheck disable=SC2034
  COMPONENT_VERSION="$(jq -r ".extensions[\"$NAME\"]
    | .versions[] | select(.version == \"$VERSION\")
    | .componentVersion | if . != null then . else \"\" end" "$TARGET_PATH/extensions-unwrapped-hashes.json")"
  if [ -z "$COMPONENT_VERSION" ]
  then
    # TODO: this is a mock that have to be fixed
    # in the extensions repository by adding a
    # .extensions[].versions[].componentVersion field
    # in the hashes.json index
    COMPONENT_VERSION="$VERSION"
    case "$COMPONENT" in
      (core|contrib)
        COMPONENT_VERSION="$COMPONENT"
        ;;
      (citus)
        COMPONENT_VERSION="10.1.2"
        ;;
      (healpix)
        # shellcheck disable=SC2034
        COMPONENT_VERSION="1.0"
        ;;
    esac
  fi
  if [ -z "$COMPONENT_VERSION" ]
  then
    echo "Can not retrieve component version for extension $REPOSITORY $PUBLISHER $NAME $VERSION $FLAVOR$POSTGRES_VERSION $BUILD $BUILD_ARCH $BUILD_OS"
    return 1
  fi
  # shellcheck disable=SC2034
  HASH="$(jq -r ".extensions[\"$NAME\"]
    | .versions[] | select(.version == \"$VERSION\")
    | .availableFor[] | select(.postgresVersion == \"$POSTGRES_VERSION\"
      and (.flavor == \"$FLAVOR\" or (.flavor == null and \"$FLAVOR\" == \"$EXTENSIONS_DEFAULT_FLAVOR\")))
      and (.build == \"$BUILD\" or (.build == null and \"$BUILD\" == \"\")))
    | .buildHash" "$TARGET_PATH/extensions-unwrapped-hashes.json")"
  if [ -z "$HASH" ]
  then
    echo "Can not retrieve hash for extension $REPOSITORY $PUBLISHER $NAME $VERSION $FLAVOR$POSTGRES_VERSION $BUILD $BUILD_ARCH $BUILD_OS"
    return 1
  fi
  # shellcheck disable=SC2034
  POSTGRES_EXACT_VERSION="$(jq -r ".extensions[\"plpgsql\"]
    | .versions[]
    | .availableFor[] | select(.postgresVersion == \"$POSTGRES_VERSION\"
      and (.flavor == \"$FLAVOR\" or (.flavor == null and \"$FLAVOR\" == \"$EXTENSIONS_DEFAULT_FLAVOR\")))
      and (.build == \"$BUILD\" or (.build == null and \"$BUILD\" == \"\")))
    | .postgresVersion" "$TARGET_PATH/extensions-unwrapped-hashes.json")"
  if [ -z "$POSTGRES_EXACT_VERSION" ]
  then
    # TODO: this is a mock that have to be fixed
    # in the extensions repository by adding a
    # .extensions[].versions[].availableFor[].postgresExactVersion field
    # in the hashes.json index
    POSTGRES_EXACT_VERSION="$POSTGRES_VERSION"
    case "$POSTGRES_VERSION" in
      (11)
        POSTGRES_EXACT_VERSION="11.13"
        ;;
      (12)
        POSTGRES_EXACT_VERSION="12.8"
        ;;
      (13)
        # shellcheck disable=SC2034
        POSTGRES_EXACT_VERSION="13.4"
        ;;
    esac
  fi
  if [ -z "$POSTGRES_EXACT_VERSION" ]
  then
    echo "Can not retrieve postgres exact version for extension $REPOSITORY $PUBLISHER $NAME $VERSION $FLAVOR$POSTGRES_VERSION $BUILD $BUILD_ARCH $BUILD_OS"
    return 1
  fi
  IMAGE_NAME="$(eval "echo \"$IMAGE_TEMPLATE\"")"
  printf '%s\n' "$IMAGE_NAME"
}

is_image_repository_url() {
  printf '%s' "$1" | grep -q "[?&]imageTemplate="
}

is_image_repository_with_regcred_secret_url() {
  printf '%s' "$1" | grep "[?&]imageTemplate=" | grep -q "[?&]imageRegcredSecret="
}

is_proxied_repository_url() {
  printf '%s' "$1" | grep -q "[?&]proxyUrl="
}

get_proxy_url_from_url() {
  local PROXY_URL
  PROXY_URL="$(printf '%s' "$1" | grep "[?&]proxyUrl=")"
  PROXY_URL="$(printf '%s' "$PROXY_URL" \
    | sed 's/^.*[?&]proxyUrl=\([^?&]\+\)\([?&].*\)\?$/\1/')"
  PROXY_URL="$(printf '%s' "$PROXY_URL" | urldecode)"
  printf '%s' "$PROXY_URL"
}

get_image_template_from_url() {
  local IMAGE_TEMPLATE
  IMAGE_TEMPLATE="$(printf '%s' "$1" | grep "[?&]imageTemplate=")"
  IMAGE_TEMPLATE="$(printf '%s' "$IMAGE_TEMPLATE" \
    | sed 's/^.*[?&]imageTemplate=\([^?&]\+\)\([?&].*\)\?$/\1/')"
  IMAGE_TEMPLATE="$(printf '%s' "$IMAGE_TEMPLATE" | urldecode)"
  # shellcheck disable=SC2016
  if printf '%s' "$IMAGE_TEMPLATE" | grep -q '\([`;"]\|\$(\)'
  then
    # shellcheck disable=SC2016
    echo 'Forbidden strings [`|;|"|$(] found in parameter imageTemplate of URL '"$1" >&2
    return 1
  fi
  printf '%s' "$IMAGE_TEMPLATE"
}

get_image_regcred_secret_from_url() {
  local REPOSITORY_CREDENTIAL_SECRET
  REPOSITORY_CREDENTIAL_SECRET="$(printf '%s' "$1" | grep "[?&]imageRegcredSecret=")"
  REPOSITORY_CREDENTIAL_SECRET="$(printf '%s' "$REPOSITORY_CREDENTIAL_SECRET" \
    | sed 's/^.*[?&]imageRegcredSecret=\([^?&]\+\)\([?&].*\)\?$/\1/')"
  REPOSITORY_CREDENTIAL_SECRET="$(printf '%s' "$REPOSITORY_CREDENTIAL_SECRET" | urldecode)"
  printf '%s' "$REPOSITORY_CREDENTIAL_SECRET"
}

setup_extensions_secrets() {
  if is_image_repository_with_regcred_secret_url "$EXTENSIONS_REPOSITORY_URL"
  then
    local IMAGE_TEMPLATE REPOSITORY_CREDENTIAL_SECRET REPOSITORY_CREDENTIAL
    IMAGE_TEMPLATE="$(get_image_template_from_url "$EXTENSIONS_REPOSITORY_URL")"
    REPOSITORY_CREDENTIAL_SECRET="$(get_image_regcred_secret_from_url "$EXTENSIONS_REPOSITORY_URL")"
    REPOSITORY_CREDENTIAL="$(jq -r '.auths|to_entries|.[]|.key + "|" + .value.auth' "${HOME}/.docker/config.json")"
    REPOSITORY_CREDENTIAL="$(printf '%s' "$REPOSITORY_CREDENTIAL" | grep -F "${IMAGE_TEMPLATE%%/*}")"
    REPOSITORY_CREDENTIAL="$(printf '%s' "$REPOSITORY_CREDENTIAL" | head -n 1 | cut -d '|' -f 2 | base64 -d)"
    REPOSITORY_CREDENTIAL="$(jq -r '.auths|to_entries|.[]|.key + "|" + .value.auth' "${HOME}/.docker/config.json" \
      | grep -F "${IMAGE_TEMPLATE%%/*}" | head -n 1 | cut -d '|' -f 2 | base64 -d)"
    kubectl delete secret --ignore-not-found -n "$OPERATOR_NAMESPACE" "$REPOSITORY_CREDENTIAL_SECRET"
    kubectl create secret docker-registry -n "$OPERATOR_NAMESPACE" "$REPOSITORY_CREDENTIAL_SECRET" \
      --docker-server="${IMAGE_TEMPLATE%%/*}" \
      --docker-username="${REPOSITORY_CREDENTIAL%:*}" \
      --docker-password="${REPOSITORY_CREDENTIAL#*:}"
  fi
}

